/**
  ******************************************************************************
  * @author     Chris
  * @since      2024/6/24 23:23
  *
  * @file       pd_spi_hd.c
  * @brief      SPI peripheral hardware driver.
  *
  * @note       This file contains the hardware driver for the SPI peripheral.
  *
  * @warning    None.
  ******************************************************************************
  * Change Logs:
  *   Date          Author       Notes
  *   2024/6/24     Chris        the first version
  *
  ******************************************************************************
  */


#include <stm32f4xx_ll_gpio.h>
#include <stm32f4xx_ll_spi.h>
#include <stm32f4xx_ll_bus.h>
#include "pd_spi_hd.h"

#define SSEL(x, y)  SPIHardware_writeBit(x, y)

static void SPIHardware_writeBit(const GPIO *gpio, const FlagStatus BitVal) {
    if (gpio) gpio->GPIOx->BSRR = BitVal ? gpio->Pin : gpio->Pin << 16;
}

static void SPIHardware_init_(const SPI *super) {
    const SPIHardware *this = (SPIHardware *)super;

    if      (this->SPIx == SPI1) LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI1);
    else if (this->SPIx == SPI2) LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_SPI2);
    else if (this->SPIx == SPI3) LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_SPI3);

    for (int i = 0; i < super->sselSize; ++i) GPIO_init(&super->ssel[i]);
    if (this->spi.sclk) GPIO_init(this->spi.sclk);
    if (this->spi.mosi) GPIO_init(this->spi.mosi);
    if (this->spi.miso) GPIO_init(this->spi.miso);

    LL_SPI_InitTypeDef spiInitTypeDef = {0};
    LL_SPI_StructInit(&spiInitTypeDef);
    spiInitTypeDef.TransferDirection = this->TransferDirection;
    spiInitTypeDef.Mode              = this->Mode;
    spiInitTypeDef.DataWidth         = this->DataWidth;
    spiInitTypeDef.ClockPolarity     = this->ClockPolarity;
    spiInitTypeDef.ClockPhase        = this->ClockPhase;
    spiInitTypeDef.NSS               = this->NSS;
    spiInitTypeDef.BaudRate          = this->BaudRate;
    spiInitTypeDef.BitOrder          = this->BitOrder;
    spiInitTypeDef.CRCCalculation    = this->CRCCalculation;
    spiInitTypeDef.CRCPoly           = this->CRCPoly;
    LL_SPI_Init(this->SPIx, &spiInitTypeDef);

    LL_SPI_Enable(this->SPIx);

    // Set all slave select
    for (int i = 0; i < super->sselSize; ++i) SSEL(&super->ssel[i], 1);

}

static void SPIHardware_start_(const SPI *super, const GPIO *ssel) {
    SSEL(ssel, 0); // Slave select enable
}

static void SPIHardware_stop_(const SPI *super, const GPIO *ssel) {
    SSEL(ssel, 1); // Slave select disable
}

static uint8_t SPIHardware_swapByte_(const SPI *super, const uint8_t dat) {
    const SPIHardware *this = (SPIHardware *)super;

    // Wait transmit empty
    while (!LL_SPI_IsActiveFlag_TXE(this->SPIx)) {
    }
    LL_SPI_TransmitData8(this->SPIx, dat); // Send 8 bits data

    while (LL_SPI_IsActiveFlag_BSY(this->SPIx)) {
    }

    // Wait receive not empty
    while (!LL_SPI_IsActiveFlag_RXNE(this->SPIx)) {
    }
    const uint8_t ret = LL_SPI_ReceiveData8(this->SPIx);

    return ret;
}

static void SPIHardware_preConfigGPIO(GPIO *gpio, const SPI_TypeDef *SPIx, const uint32_t NSS) {
    if (NSS == LL_SPI_NSS_SOFT) {
        // Set general GPIO if the slave select is control by software
        gpio->Mode = LL_GPIO_MODE_OUTPUT;
        gpio->Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
        gpio->Pull = LL_GPIO_PULL_UP;
    } else {
        // Set alternate GPIO if the slave select is control by hardware
        gpio->Mode = LL_GPIO_MODE_ALTERNATE;
        gpio->Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
        gpio->Pull = LL_GPIO_PULL_NO;
        if (SPIx == SPI3) gpio->Alternate = LL_GPIO_AF_6;
        else if (SPIx == SPI1 || SPIx == SPI2) gpio->Alternate = LL_GPIO_AF_5;
        else {
        }
    }
}


void SPIHardware_constructor(SPIHardware *this, SPI_TypeDef *SPIx,
                             GPIO *ssel, const uint8_t sselSize, GPIO *sclk, GPIO *mosi, GPIO *miso) {
    // SPI GPIO pre-config
    GPIO *gpios[] = {sclk, mosi, miso};
    for (int i = 0; i < 3; ++i) SPIHardware_preConfigGPIO(gpios[i], SPIx, LL_SPI_NSS_HARD_OUTPUT);

    for (int i = 0; i < sselSize; ++i) SPIHardware_preConfigGPIO(&ssel[i], SPIx, LL_SPI_NSS_SOFT);

    // Super class' virtual table
    static struct SPIVtbl vtbl = {
        .init      = &SPIHardware_init_,
        .start     = &SPIHardware_start_,
        .swap_byte = &SPIHardware_swapByte_,
        .stop      = &SPIHardware_stop_
    };
    SPI_constructor(&this->spi, ssel, sselSize, sclk, mosi, miso);

    this->spi.vptr = &vtbl;

    this->SPIx              = SPIx;
    this->TransferDirection = LL_SPI_FULL_DUPLEX;
    this->Mode              = LL_SPI_MODE_MASTER;
    this->DataWidth         = LL_SPI_DATAWIDTH_8BIT;
    this->ClockPolarity     = LL_SPI_POLARITY_HIGH;
    this->ClockPhase        = LL_SPI_PHASE_2EDGE;
    this->NSS               = LL_SPI_NSS_SOFT;
    this->BaudRate          = LL_SPI_BAUDRATEPRESCALER_DIV2;
    this->BitOrder          = LL_SPI_MSB_FIRST;
    this->CRCCalculation    = LL_SPI_CRCCALCULATION_DISABLE;
    this->CRCPoly           = 10U;
}
